<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <style>

        .container1 {
            display: flex;
            flex-wrap: wrap;
            height: 95vh;
            padding: 5px;
        }

        .container2-1 {
            display: flex;
            width: 20%;
            flex-wrap: wrap;
            border: 3px solid tan;
            border-radius: 5px;
            flex-direction: column;
            padding: 5px;
        }

        .container2-2 {
            display: flex;
            flex-wrap: wrap;
            width: 77%;
            border: 3px solid tan;
            border-radius: 5px;
            justify-content: center;
            align-items: center;
        }

        .container3 {
            display: flex;
            flex-wrap: wrap;
            justify-content: center;
            align-items: center;
        }

    </style>
    <script src="str-util.js"></script>
</head>
<body>
<div class="container1">
    <div class="container2-1">
        <input type="file" id="imgFile" onchange="upload()"/>
        <p>游戏图片:</p>
        <img id="backImg" src="" style="height: 150px;">
        <div>
            切割规则:
            <input type="text" id="x" style="width: 8%;"/>
            x
            <input type="text" id="y" style="width: 8%;"/>
        </div>
        <button style="margin-top: 5px; height: 46px;" onclick="play()">开始游戏</button>
        <p>
            游戏目前存在bug:
        </p>
        <ul>
            <li>随机排序未按照相邻位置排序, 导致切分的图片可能无法复原, 测试游戏规则时建议2x2</li>
            <li>拖动功能未实现</li>
            <li>是以数组驱动html的方式进行开发的, html页面元素不变, 变化的是其背景图片</li>
        </ul>
    </div>
    <div id="container" class="container2-2">
    </div>
</div>


<script type="text/javascript">

    let gameObjectArray = [];
    const IMG_ID_PREFIX = "imgIdPrefix";
    let IMG_FILE_URL = null;

    function upload() {
        console.log("sss");
        IMG_FILE_URL = window.URL.createObjectURL(document.getElementById('imgFile').files[0]);
        document.getElementById('backImg').src = IMG_FILE_URL;
    }

    function play() {
        debugger;
        // 没有这个会出现异常, 运行前必须先初始化
        gameObjectArray = [];
        const imgPath = document.getElementById('imgFile').value;
        if (STR_UTIL.isEmpty(imgPath)) {
            alert("请先选择图片!");
            return;
        }
        const x = document.getElementById('x').value;
        if (STR_UTIL.isEmpty(x)) {
            alert("请设置切割参数!");
            return;
        }
        if (!/\d/.test(x)) {
            alert("切割参数设置错误, 只能是数字!");
            return;
        }

        const y = document.getElementById('y').value;
        if (STR_UTIL.isEmpty(y)) {
            alert("请先切割参数!");
            return;
        }
        if (!/\d/.test(y)) {
            alert("切割参数设置错误, 只能是数字!");
            return;
        }

        const imgWidth = document.getElementById("backImg").naturalWidth;
        const imgHeight = document.getElementById("backImg").naturalHeight;

        const oneNodeWidth = parseInt(imgWidth / x) - 3;
        const oneNodeHeight = parseInt(imgHeight / y) - 3;

        // 随机隐藏一个图, 例如: 切割3x3, 则要随机隐藏一个坐标
        const hiddenX = Math.floor(Math.random() * getsTheNearestInteger(x)) % x;
        const hiddenY = Math.floor(Math.random() * getsTheNearestInteger(y)) % y;
        const rotationAngle = Math.floor(Math.random() * 10) % 4; // 1: 旋转0度, 1: 旋转90度, 2: 旋转180度, 3:旋转270度

        let baseX = 0;
        let baseY = 0;

        var baseHtmlImgObj = ``;

        for (let i = 0; i < y; i++) {
            gameObjectArray.push([]);
            baseY = -1 * (oneNodeHeight * i);
            for (let j = 0; j < x; j++) {
                let gameObj = new GameObject();
                baseX =  -1 * (oneNodeWidth * j);
                gameObj.currentNodeId = IMG_ID_PREFIX + i + j;
                gameObj.hidden = false;
                gameObj.row = i;
                gameObj.column = j;
                gameObj.background = `transparent url(${IMG_FILE_URL}) no-repeat scroll ${baseX}px ${baseY}px`;

                let htmlImgObj = `
                <div id="${gameObj.currentNodeId}" style="background: transparent url(${IMG_FILE_URL}) no-repeat scroll ${baseX}px ${baseY}px;
            height: ${oneNodeHeight}px;
            width: ${oneNodeWidth}px;
            border: 1px solid white;
            flex-shrink: 0;" onclick="move(${i}, ${j})"></div>
                `;

                // 隐藏的那个元素
                if (j === hiddenX && i === hiddenY) {
                    gameObj.hidden = true;
                    gameObj.background = "white";
                }

                gameObjectArray[i].push(gameObj);
                baseHtmlImgObj += htmlImgObj;
            }
        }

        let baseContainerHtml = `
        <div style="width: ${imgWidth}px; height: ${imgHeight}px;display: flex;
            flex-wrap: wrap;
            justify-content: center;
            align-items: center;">
            ${baseHtmlImgObj}
        </div>
        `;

        document.getElementById("container").innerHTML = "";
        document.getElementById("container").innerHTML = baseContainerHtml;

        gameObjectRandomSort(x, y);
        structureBuild();
        syncGameObjectArray2html();
    }

    /**
     * 功能: 同步背景到html中
     */
    function syncGameObjectArray2html() {
        for (let i = 0; i < gameObjectArray.length; i++) {
            let row = gameObjectArray[i];
            for (let j = 0; j < row.length; j++) {
                let gameObjTmp = row[j];
                document.getElementById(IMG_ID_PREFIX + i + j).style.background = gameObjTmp.background;
            }
        }
    }

    /**
     * 功能: 图片移动事件方法
     */
    function move(i, j) {
        debugger;
        let gameObj = gameObjectArray[i][j];
        if (gameObj.hidden) {
            return;
        }

        let moveX = null;
        let moveY = null;

        if (gameObj.leftNodeId) {
            let leftNode = gameObjectArray[i][j - 1];
            if (leftNode.hidden) {
                moveX = j - 1;
                moveY = i;
            }
        }

        if (gameObj.rightNodeId) {
            let rightNode = gameObjectArray[i][j + 1];
            if (rightNode.hidden) {
                moveX = j + 1;
                moveY = i;
            }
        }

        if (gameObj.upNodeId) {
            let upNode = gameObjectArray[i - 1][j];
            if (upNode.hidden) {
                moveX = j;
                moveY = i - 1;
            }
        }

        if (gameObj.downNodeId) {
            let downNode = gameObjectArray[i + 1][j];
            if (downNode.hidden) {
                moveX = j;
                moveY = i + 1;
            }
        }

        if (moveX !== null && moveY !== null) {
            swap(j, i, moveX, moveY);
            structureBuild();
            syncGameObjectArray2html();
        }

        isWin()
    }

    /**
     * 功能: 判断是否胜利
     */
    function isWin() {
        for (let i = 0; i < gameObjectArray.length; i++) {
            let row = gameObjectArray[i];
            for (let j = 0; j < row.length; j++) {
                let gameObjTmp = row[j];
                if (gameObjTmp.row !== i || gameObjTmp.column !== j) {
                    return;
                }
            }
        }
        alert("恭喜你, 通关了!");
    }

    /**
     * 功能: 随机打乱节点排序
     * 备注:
     * 1. 这种打乱方式将会导致被打乱的图片无法复原, 更好的做法应该使打乱相邻节点
     */
    function gameObjectRandomSort(x, y) {
        // 随机打乱次数为切割次数除以2
        for (let i = 0; i < (x * y / 2); i++) {
            const randomX1 = Math.floor(Math.random() * getsTheNearestInteger(x)) % x;
            const randomY1 = Math.floor(Math.random() * getsTheNearestInteger(y)) % y;

            const randomX2 = Math.floor(Math.random() * getsTheNearestInteger(x)) % x;
            const randomY2 = Math.floor(Math.random() * getsTheNearestInteger(y)) % y;

            debugger;
            swap(randomX1, randomY1, randomX2, randomY2);
        }
    }

    /**
     * 功能: 交换两个节点的数据
     */
    function swap(randomX1, randomY1, randomX2, randomY2) {
        let gameObject1 = gameObjectArray[randomY1][randomX1];
        let gameObject2 = gameObjectArray[randomY2][randomX2];

        // 交换数据信息
        const tempCurrentNodeId = gameObject1.currentNodeId;
        gameObject1.currentNodeId = gameObject2.currentNodeId;
        gameObject2.currentNodeId = tempCurrentNodeId;

        const tempHidden = gameObject1.hidden;
        gameObject1.hidden = gameObject2.hidden;
        gameObject2.hidden = tempHidden;

        const tempRow = gameObject1.row;
        gameObject1.row = gameObject2.row;
        gameObject2.row = tempRow;

        const tempColumn = gameObject1.column;
        gameObject1.column = gameObject2.column;
        gameObject2.column = tempColumn;

        const tempBackground = gameObject1.background;
        gameObject1.background = gameObject2.background;
        gameObject2.background = tempBackground;
    }

    /**
     * 功能: 获取最接近的那个10的整倍数, 例如23接近100, 89接近100, 123接近1000
     */
    function getsTheNearestInteger(num) {
        let baseInt = 1;
        while (baseInt < num) {
            baseInt *= 10;
        }
        return baseInt;
    }

    /**
     * 功能: 组织好存放图片数据的数据结构
     */
    function structureBuild() {
        for (let i = 0; i < gameObjectArray.length; i++) {
            let preRow = (i === 0 ? null : gameObjectArray[i - 1]);
            let row = gameObjectArray[i];
            let nextRow = (i === gameObjectArray.length - 1 ? null: gameObjectArray[i + 1]);

            for (let j = 0; j < row.length; j++) {
                let gameObjTmp = row[j];
                if (preRow) {
                    gameObjTmp.upNodeId = preRow[j].currentNodeId;
                } else {
                    gameObjTmp.upNodeId = null;
                }

                if (nextRow) {
                    gameObjTmp.downNodeId = nextRow[j].currentNodeId;
                } else {
                    gameObjTmp.downNodeId = null;
                }

                if (j === 0) {
                    gameObjTmp.leftNodeId = null;
                    gameObjTmp.rightNodeId = row[j + 1].currentNodeId;
                } else if (j === row.length - 1) {
                    gameObjTmp.leftNodeId = row[j - 1].currentNodeId;
                    gameObjTmp.rightNodeId = null;
                } else {
                    gameObjTmp.leftNodeId = row[j - 1].currentNodeId;
                    gameObjTmp.rightNodeId = row[j + 1].currentNodeId;
                }
            }
        }
    }

    class GameObject {

        upNodeId;

        downNodeId;

        leftNodeId;

        rightNodeId;

        currentNodeId;

        // 是否是隐藏元素
        hidden;

        // 背景图片url
        background;

        // 当前数组元素节点一层下标, 判断胜利条件的根据
        row;

        // 当前数组元素节点二层下标, 判断胜利条件的根据
        column;
    }


</script>
</body>
</html>